<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>流式交互 + 实时朗读</title>
    <style>
      .container {
        max-width: 800px;
        margin: 20px auto;
        padding: 20px;
      }
      #result {
        white-space: pre-line;
        margin: 20px 0;
        padding: 20px;
        border: 1px solid #ccc;
        min-height: 200px;
        border-radius: 4px;
        background-color: #f9f9f9;
        font-size: 16px;
        line-height: 1.6;
      }
      #prompt {
        width: 100%;
        padding: 15px;
        margin-bottom: 15px;
        border: 1px solid #ddd;
        border-radius: 8px;
        min-height: 100px;
        font-size: 16px;
        resize: vertical;
        box-sizing: border-box;
      }
      .button-group {
        display: flex;
        gap: 10px;
        margin-bottom: 15px;
      }
      button {
        padding: 10px 20px;
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        font-size: 16px;
        transition: background-color 0.3s;
        flex: 1;
      }
      button:hover {
        background-color: #0056b3;
      }
      button:disabled {
        background-color: #cccccc;
        cursor: not-allowed;
      }
      #stop-button {
        background-color: #dc3545;
      }
      #stop-button:hover {
        background-color: #bd2130;
      }
      #loading {
        display: none;
        margin: 15px 0;
        color: #666;
        font-size: 16px;
      }
      .audio-controls {
        margin-top: 20px;
        display: none;
      }
      .status-bar {
        margin-top: 15px;
        padding: 10px;
        border-radius: 6px;
        background-color: #f0f0f0;
        font-size: 14px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>流式输出 + 语音朗读测试</h1>
      <textarea id="prompt" placeholder="请输入你的问题..."></textarea>

      <div class="button-group">
        <button onclick="startStreaming()" id="start-button">开始流式输出</button>
        <button onclick="closeConnection()" id="stop-button">停止</button>
      </div>

      <div id="loading">正在处理您的请求，请稍候...</div>
      <div id="audio-status" class="status-bar"></div>

      <div id="result"></div>

      <div class="audio-controls" id="audio-controls">
        <audio id="audio-player" controls></audio>
        <button onclick="playAudio()" id="play-audio-btn" style="margin-top: 10px">播放语音</button>
        <button onclick="downloadAudio()" id="download-audio-btn" style="margin-top: 10px">下载语音</button>
      </div>
    </div>

    <script>
      // 全局状态管理（优化：增加文本收集逻辑）
      const state = {
        abortController: null,
        synth: window.speechSynthesis,
        utteranceQueue: [],
        isSpeaking: false,
        fullText: '', // 用于收集完整文本（即使后端未发送full_text）
        audioUrl: null,
        isStreamingComplete: false // 标记流式是否完成
      }

      // 关闭现有连接并停止语音（优化：重置更多状态）
      function closeConnection() {
        if (state.abortController) {
          state.abortController.abort()
          state.abortController = null
        }

        state.synth.cancel()
        state.utteranceQueue = []
        state.isSpeaking = false
        state.fullText = ''
        state.audioUrl = null
        state.isStreamingComplete = false

        document.getElementById('loading').style.display = 'none'
        document.getElementById('start-button').disabled = false
        document.getElementById('audio-controls').style.display = 'none'
        updateStatus('操作已停止')
      }

      // 更新状态显示（优化：更清晰的状态描述）
      function updateStatus(message) {
        document.getElementById('audio-status').innerText = `状态：${message}`
      }

      // 处理朗读队列（保持不变）
      function speakNext() {
        if (state.utteranceQueue.length === 0) {
          state.isSpeaking = false
          return
        }

        state.isSpeaking = true
        const text = state.utteranceQueue.shift()
        const utterance = new SpeechSynthesisUtterance(text)
        utterance.lang = 'zh-CN'
        utterance.rate = 1.0
        utterance.pitch = 1.0

        utterance.onend = () => {
          setTimeout(speakNext, 300)
        }

        state.synth.speak(utterance)
      }

      // 播放完整语音（优化：自动播放）
      function playAudio() {
        const audioPlayer = document.getElementById('audio-player')
        if (state.audioUrl) {
          audioPlayer.src = state.audioUrl
          audioPlayer.play().catch(e => {
            console.error('播放失败:', e)
            updateStatus(`播放失败: ${e.message}`)
          })
        } else {
          updateStatus('没有可用的音频')
        }
      }

      // 下载音频（保持不变）
      function downloadAudio() {
        if (!state.audioUrl) {
          updateStatus('没有可用的音频')
          return
        }

        const link = document.createElement('a')
        link.href = state.audioUrl
        link.download = 'conversation-audio.mp3'
        document.body.appendChild(link)
        link.click()
        document.body.removeChild(link)
      }

      // 获取完整语音（优化：更健壮的错误处理）
      function getFullAudio() {
        if (!state.fullText) {
          updateStatus('没有收集到完整文本，无法生成语音')
          return
        }

        updateStatus('正在生成语音...')

        fetch('http://localhost:5000/api/tts', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization:
              'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc1MzUzNzY3MiwianRpIjoiZTcxZmY0ZWEtOGQyYy00Y2I0LWEyYTUtMmYzMjkxMzk3Y2U1IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImVhOTI1OTViLTBjMDEtNGI3MS1iMDE3LTI0YWY5NDI3NGMzZiIsIm5iZiI6MTc1MzUzNzY3MiwiY3NyZiI6ImNkY2VmZDAyLWMwNzYtNGZhNi1hMmY4LTg5MjE2MzkwN2Q0ZiIsImV4cCI6MTc1MzU0NDg3Mn0.QOdXvC04eXLjporr0QAWeOuKFCvm_4wl3dhyuJ9dELk'
          },
          body: JSON.stringify({ text: state.fullText })
        })
          .then(response => {
            if (!response.ok) {
              throw new Error(`TTS请求失败（状态码：${response.status}）`)
            }
            return response.blob()
          })
          .then(blob => {
            state.audioUrl = URL.createObjectURL(blob)
            document.getElementById('audio-controls').style.display = 'block'
            // 自动播放语音（优化：提升用户体验）
            playAudio()
            updateStatus('语音生成完成（已自动播放）')
          })
          .catch(error => {
            console.error('获取语音失败:', error)
            updateStatus(`语音生成失败: ${error.message}`)
          })
      }

      // 开始流式请求（优化：更清晰的流程）
      function startStreaming() {
        // 重置状态
        closeConnection()

        // 显示加载状态
        document.getElementById('loading').style.display = 'block'
        document.getElementById('start-button').disabled = true
        updateStatus('正在发起请求...')

        // 获取输入内容
        const prompt = document.getElementById('prompt').value.trim()
        if (!prompt) {
          alert('请输入问题内容')
          document.getElementById('loading').style.display = 'none'
          document.getElementById('start-button').disabled = false
          return
        }

        // 创建AbortController
        state.abortController = new AbortController()

        // 发起POST请求
        fetch('http://localhost:5000/api/conversation', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization:
              'Bearer ' +
              'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc1MzUzNzY3MiwianRpIjoiZTcxZmY0ZWEtOGQyYy00Y2I0LWEyYTUtMmYzMjkxMzk3Y2U1IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImVhOTI1OTViLTBjMDEtNGI3MS1iMDE3LTI0YWY5NDI3NGMzZiIsIm5iZiI6MTc1MzUzNzY3MiwiY3NyZiI6ImNkY2VmZDAyLWMwNzYtNGZhNi1hMmY4LTg5MjE2MzkwN2Q0ZiIsImV4cCI6MTc1MzU0NDg3Mn0.QOdXvC04eXLjporr0QAWeOuKFCvm_4wl3dhyuJ9dELk'
          },
          body: JSON.stringify({
            message: prompt,
            model_key: 'deepseek-ai/DeepSeek-V3',
            conversation_id: 'conv_c8508be1-4f05-443e-953d-83c68b24afea'
          }),
          signal: state.abortController.signal
        })
          .then(response => {
            // 检查响应合法性
            if (!response.ok) {
              throw new Error(`请求失败（状态码：${response.status}）`)
            }
            if (!response.headers.get('content-type').includes('text/event-stream')) {
              throw new Error('非流式响应（请检查后端配置）')
            }

            // 开始读取流
            const reader = response.body.getReader()
            const decoder = new TextDecoder('utf-8')
            let buffer = ''
            updateStatus('正在接收流式数据...')

            // 递归读取流数据（优化：更清晰的流处理逻辑）
            function read() {
              return reader.read().then(({ done, value }) => {
                // 处理流结束
                if (done) {
                  state.isStreamingComplete = true
                  document.getElementById('loading').style.display = 'none'
                  document.getElementById('start-button').disabled = false
                  updateStatus('流式传输完成（正在生成语音...）')
                  // 触发语音生成（即使后端未发送full_text）
                  getFullAudio()
                  return
                }

                // 解码数据并处理缓冲区
                buffer += decoder.decode(value, { stream: true })
                while (buffer.includes('\n\n')) {
                  const eventEndIndex = buffer.indexOf('\n\n')
                  const eventData = buffer.substring(0, eventEndIndex)
                  buffer = buffer.substring(eventEndIndex + 2)

                  // 处理每个SSE事件
                  eventData.split('\n').forEach(line => {
                    if (line.startsWith('data: ')) {
                      const jsonStr = line.substring(6).trim()
                      if (jsonStr === '[DONE]') {
                        state.isStreamingComplete = true
                        updateStatus('流式传输完成（正在生成语音...）')
                        // 触发语音生成（即使后端未发送full_text）
                        getFullAudio()
                        return
                      }
                      // 解析JSON数据
                      try {
                        const jsonData = JSON.parse(jsonStr)
                        // 处理文本片段
                        if (jsonData.text || jsonData.chunk) {
                          const textChunk = jsonData.text || jsonData.chunk
                          // 更新页面显示
                          document.getElementById('result').innerText += textChunk
                          // 收集完整文本
                          state.fullText += textChunk
                          // 添加到朗读队列
                          state.utteranceQueue.push(textChunk)
                          if (!state.isSpeaking) {
                            speakNext()
                          }
                        }
                        // 处理后端发送的full_text（可选）
                        if (jsonData.full_text) {
                          state.fullText = jsonData.full_text
                        }
                      } catch (e) {
                        console.error('解析JSON失败:', e, '原始数据:', jsonStr)
                      }
                    }
                  })
                }

                // 继续读取流
                return read()
              })
            }

            // 开始读取流
            return read()
          })
          .catch(error => {
            if (error.name !== 'AbortError') {
              console.error('请求失败:', error)
              document.getElementById('loading').style.display = 'none'
              document.getElementById('start-button').disabled = false
              updateStatus(`请求失败: ${error.message}`)
            }
          })
      }

      // 页面关闭时关闭连接（保持不变）
      window.onbeforeunload = closeConnection
    </script>
  </body>
</html>
